/**
 * @name Tooltip
 * @description Responsible for provide item tooltip functionality
 */

jt.module('Tooltip', function(context) {
	
		// Useful shorthands
	var canvas = context.canvas,
		settings = context.settings,
		gh = context.settings.grid.h,
		gv = context.settings.grid.v,
		document = context.document,
		fn = context.fn,
		TWEEN = context.require('TWEEN'),

		options = util.extend({
            enabled: true
        }, settings.Tooltip || {}),

		tooltipsRef = [],
		activeTooltip,
		mouseX,
		mouseY;
	
	context.on('init', function() {
		context.log('[Tooltip] init');
	});

	var Tooltip = function(options) {
		
		if(!(this instanceof Tooltip)) {
			var tooltip =  new Tooltip(options);
			tooltipsRef.push(tooltip)
			return tooltip;
		}

		var o = this._options = util.extend({
			defaultSide: 'right',
			followMouse: true,
			showArrow: false,
			offsetLeft: 20,
			layerOffsetTop: 0,
			layerOffsetLeft: 0,
			offsetRight: 30,
			offsetTop: 0,
			offsetTopMargin: 0,
			marginUp: 30,
			marginDown: 30,
			marginLeft: 30,
			margiinRight: 30,
			showDelay: 0,
			hideDelay: 0,
			fadeInSpeed: 200,
			fadeOutSpeed: 0,
			showOffsetTop: 0,
			hideOffsetTop: 0,
			arrowHeight: 15,
			hideOnOver: false,
			toOpacity: 1
		}, options),

		body = document.getElementsByTagName('body')[0],
		tooltip = document.createElement('div'),
		htmlArrow = document.createElement('div'),
		htmlArrowBorder = document.createElement('div');

		body.appendChild(tooltip);

		util.addClass(tooltip, 'jt-tooltip');
		util.addClass(htmlArrow, 'arrow');
		util.addClass(htmlArrowBorder, 'arrowBorder');


		o.hideOnOver === true && util.addEvent(tooltip, 'mousemove', function() {
			tooltip.style.display = 'none';
		})

		this._getTooltipHtml = function() {
			return tooltip;
		}

		this._getArrow = function() {
			return htmlArrow;
		}

		this._getArrowBorder = function() {
			return htmlArrowBorder;
		}

		return this;
	}

	Tooltip.prototype = {

		_elements: {},

		_style: {
			opacity: 0
		},
		
		addElement: function(name, options) {
			
			var tooltip = this._getTooltipHtml();

			if(util.is(name, 'string') && !this._elements.hasOwnProperty(name)) {
				
				var el = util.extend({
					html:  '<div class="{0}"><%=val%></div>'.format(name),
					orderUp: Object.keys(this._elements).length,
					orderDown: Object.keys(this._elements).length
				}, util.is(options, 'object') ? options : {});

				this._elements[name] = el;		
			}

			return this;
		},

		_beforeShow: function() {},

		beforeShow: function(func) {
			if(util.is(func, 'function')) {
				this._beforeShow = func;
			}

			return this;
		},

		show: function(sender, x, y) {
			var tooltip = this._getTooltipHtml(),
				that = this,
				arrow = this._getArrow(),
				arrowBorder = this._getArrowBorder(),
				o = this._options,
				posX, 
				posY, 
				arrowX, 
				arrowY,
				canRightPos,
				x = x + o.layerOffsetLeft,
				y = y + o.layerOffsetTop;

			activeTooltip = this;
			
			if(Object.keys(this._elements).length === 0) {
				return;
			}

			// add all elements to tooltip
			
			var elSortedUp = [],
				elSortedDown = [],
				htmlUp = '',
				htmlDown = '',
				elements = {};

			for(var key in this._elements) {
				elements[key] = { 
					_visible: true
				};
			}

			this._beforeShow.call(null, elements, sender);

			for(var key in this._elements) {
				var el = this._elements[key];

				if(elements[key]._visible === false) {
					continue;
				}

				el.rendered = util.tmpl(this._elements[key].html, elements[key]);
				elSortedUp.push(el);
				elSortedDown.push(el);
			}

			elSortedUp.sort(function(a, b) {return a.orderUp - b.orderUp; });
			elSortedDown.sort(function(a, b) { return a.orderDown - b.orderDown; });

			for(var i = 0, len = elSortedUp.length; i < len; i++) if(elSortedUp[i]) {
				htmlUp += elSortedUp[i].rendered;
			}

			for(var i = 0, len = elSortedDown.length; i < len; i++) if(elSortedDown[i]) {
				htmlDown += elSortedDown[i].rendered;
			}

			// Display first to calculate bbox
			tooltip.innerHTML = htmlUp;

			util.extend(tooltip.style, {
				display: 'block',
				opacity: '0',
				top: y + 'px',
				left: x + 'px'
			});

			if (!this._options.followMouse) {

				// Calculate tooltip position
				var width = tooltip.offsetWidth,
					height = tooltip.offsetHeight,
					above = false,
					below = false;
				
				posY = y - height / 2 + o.offsetTop + o.layerOffsetTop;
			
				if(posY - window.pageYOffset < 0) {					// tooltip above viewport
					posY += (window.pageYOffset + o.layerOffsetTop - posY) + o.marginUp;
					tooltip.innerHTML = htmlDown;
					above = true;
				} else if ((window.pageYOffset +  o.layerOffsetTop + window.innerHeight) - (posY + height) < 0) { // tooltip below viewport
					posY -= (posY + height) - (window.pageYOffset + window.innerHeight) + o.marginDown;
					tooltip.innerHTML = htmlUp;
					below = true;
				}

				if(above === true) {
					if((posY - o.offsetTop + o.offsetTopMargin) - y < 0) {
						posY -= o.offsetTop;
					}
				}

				arrowY = y - posY - o.arrowHeight / 2;

				canRightPos = (((o.defaultSide === 'left') && (x - height - o.offsetLeft - window.pageXOffset) > 0) 
						|| ((o.defaultSide === 'right') && (window.pageXOffset + window.innerWidth) - (x + width + o.offsetLeft) < 0) )

				tooltip.style.top = posY + 'px';
				tooltip.style.left = canRightPos
								 ? x - width - o.offsetRight + 'px' 
								 : x + o.offsetLeft + 'px';

				
				if(o.showArrow === true) {
					tooltip.appendChild(arrowBorder);
					tooltip.appendChild(arrow);

					// calculate arrow position
					if(canRightPos) {												// right
						
						util.removeClass(arrow, 'arrowRight');
						util.addClass(arrow, 'arrowLeft');
						
						util.extend(arrow.style, {
							'top': arrowY + 'px',
						});

						util.removeClass(arrowBorder, 'arrowBorderRight');
						util.addClass(arrowBorder, 'arrowBorderLeft');

						util.extend(arrowBorder.style, {
							'top': arrowY + 'px',
						});
					} else {														// left
						
						util.removeClass(arrow, 'arrowLeft');
						util.addClass(arrow, 'arrowRight');

						util.extend(arrow.style, {
							'top': arrowY + 'px',
						});

						util.removeClass(arrowBorder, 'arrowBorderLeft');
						util.addClass(arrowBorder, 'arrowBorderRight');

						util.extend(arrowBorder.style, {
							'top': arrowY + 'px',
						});
					}
				}

			}

			// hide all other menus
			for(var i = 0, len = tooltipsRef.length; i < len; i++) if(tooltipsRef[i] !== this) {
				tooltipsRef[i].hide();
			}

			// Tween tooltip show
			that._tween && that._tween.stop();
			tooltip.style.opacity = 0;
			
			var style = {
				opacity: +tooltip.style.opacity,
				offsetTop: o.showOffsetTop
			},

			top = +tooltip.style.top.replace('px', '');
			
			if(o.delay === 0 && o.fadeInSpeed === 0){
				tooltip.style.display = 'block';
				tooltip.style.opacity = o.toOpacity;
			} else {
				that._tween = new TWEEN.Tween(style)
	     		.to({'opacity': o.toOpacity, 'offsetTop': 0}, o.fadeInSpeed)
	     		.onUpdate(function() {
	     			tooltip.style.opacity = this.opacity;
	     			//tooltip.style.top = (top - this.offsetTop) + 'px';
	     		})
	     		.onComplete(function() {
	     			tooltip.style.display = 'block';
	     		})
	     		.delay(o.showDelay)
	     		.start();
			}

			return this;
		},

		/**
		 * Hides tooltip
		 * @return {tooltip}
		 */
		hide: function() {
			var tooltip = this._getTooltipHtml(),
				o = this._options,
				that = this;

			activeTooltip = null;
			
			that._tween && that._tween.stop();

			var style = {
				opacity: +tooltip.style.opacity,
				offsetTop: 0
			},

			top = +tooltip.style.top.replace('px', '');

			that._tween = new TWEEN.Tween(style)
	     		.to({'opacity': 0, 'offsetTop': o.hideOffsetTop}, o.fadeOutSpeed)
	     		.onUpdate(function() {
	     			tooltip.style.opacity = this.opacity;
	     			tooltip.style.top = (top + this.offsetTop) + 'px';
	     		})
	     		.onComplete(function() {
	     			tooltip.style.display = 'none';
	     			tooltip.innerHTML = '';
	     		})
	     		.delay(o.hideDelay)
	     		.start();
		
     		return this;
		}
	};

	Tooltip.prototype.constructor = Tooltip;

	fn.extend({
		tooltip: util.bind(Tooltip, this)
	});

	var dragging = false;

	context.on('itemDragStart panelDragStart', function(it) {
		dragging = true;
	});

	context.on('itemDragEnd panelDragEnd', function(it) {
		dragging = false;
	});

	context.on('itemMouseOut itemSelected itemDragStart panelDragStart', function(it) {
		if(options.enabled !== true) {
			return;
		}

		for(var i = 0, len = tooltipsRef.length; i < len; i++) {
			tooltipsRef[i].hide(tooltipsRef[i]);
		}
	});

	context.on('itemMouseOver', function(item) {
		if(options.enabled !== true) {
			return;
		}

		var tooltip = item.ref.tooltip;
		if(!item.selected && dragging === false && tooltip instanceof Tooltip) {
			
			var bbox;

			if(tooltip._options.followMouse) {
				bbox = {x: mouseX, y: mouseY, width: 0, height: 0}
			} else {
				bbox = item.handle.getBBox();
			}
			
			tooltip.show(item.ref, bbox.x + bbox.width / 2, bbox.y + bbox.height / 2);	// center of item handler
		}
	});

	return {
		events: {
			mouseMove: function(e, x, y) {

				if(!activeTooltip || !activeTooltip._options.followMouse) return; 

				x = e.pageX;
				y = e.pageY;

				var tooltip = activeTooltip._getTooltipHtml(),
					o = activeTooltip._options,
					width = tooltip.offsetWidth,
					height = tooltip.offsetHeight,
					above = false,
					below = false,

					canRightPos = (((o.defaultSide === 'left') && (x - width - o.offsetLeft - window.pageXOffset) > 0) 
								|| ((o.defaultSide === 'right') && (window.pageXOffset + window.innerWidth) - (x + width + o.offsetLeft) < 0));

				mouseY =  y - height / 2 + o.offsetTop;
			
				if(mouseY - o.marginUp - o.layerOffsetTop - window.pageYOffset < 0) {					// tooltip above viewport
					mouseY += (window.pageYOffset + o.layerOffsetTop - mouseY) + o.marginUp;
					above = true;
				} else if ((window.pageYOffset + o.layerOffsetTop + window.innerHeight) - (mouseY + height + o.marginDown) < 0) { // tooltip below viewport
					mouseY -= (mouseY + height) - (window.pageYOffset + window.innerHeight) + o.marginDown;
					below = true;
				}

				if(above === true) {
					if((mouseY - o.offsetTop + o.offsetTopMargin) - y < 0) {
						mouseY -= o.offsetTop;
					}
				}

				mouseX = canRightPos
							? x - width - o.offsetRight 
							: x + o.offsetLeft;

				tooltip.style.top = mouseY + 'px';
				tooltip.style.left =  mouseX + 'px';
			}
		}
	}

});